// jTranslate.js
// A wrapper for GTranslate API v2.

(function(){

 // configuration vars
 var BASE_URL = 'https://www.googleapis.com/language/translate/v2', // base url for the api service
  DEFAULT_TARGET_LANGUAGE = 'en', // default target for the language enum. Property keys will be in English unless this is changed!
  LABEL_ORIGINAL_LANGUAGE = 'Original', // default label for original language
  VALUE_ORIGINAL_LANGUAGE = '', // default value for original language. If you want to make this more efficient, provide the language code of your original content.
  DEFAULT_ERROR_STRING = 'Well, that didn\'t work. (error code: ###ERROR_CODE###)'; // Default value for if it doesn't work.
 // end configuration vars
 
 // basic housekeeping
 if(!window['translateAPIkey']){ // check to make sure we have an api key
  throw('Unable to translate text: no API key found.');
 }


 // let's get the list of languages that we can play with!
 function getLanguageEnum(){ // get the language enum, but we're going to do something fun with it!
  var callbacksArray = window['google']['language']['callbacks'],
   params = {
    'target' : DEFAULT_TARGET_LANGUAGE,
    'callback' : 'window.google.language.callbacks['+callbacksArray.length+']'
   };
  callbacksArray.push(processLangEnum);
  goGetIt(BASE_URL + '/languages', params, 1);
 }
 
 function processLangEnum(response){
  var langs = window['google']['language']['Languages'] = {},
   goodLangs = response['data']['languages'];
  for(var i=0; i<goodLangs.length; i++){
   var lang = goodLangs[i];
   langs[lang['name']] = lang['language'];
  }
  if(window['languageEnumCompleteCallback']){ // just in case someone wants to wait for the language enum to be loaded, we'll provide a simple callback
   window['languageEnumCompleteCallback']();
  }
 }

 

 function translate(srcText, srcLang, destLang, callback){ // run basic translation(s)
  var callbacksArray = window['jeremy']['language']['callbacks'],
   serviceUrl = BASE_URL,
   params = { // process the args
    'source' : srcLang,
    'target' : destLang,
    'q' : typeof(srcText)=='string' || srcText instanceof Array ? srcText : srcText['text'],
    'callback' : 'window.google.language.callbacks['+callbacksArray.length+']',
    'format' : typeof(srcText)=='object' ? srcText['type'] : null
   };
  callbacksArray.push(callback);
  goGetIt(serviceUrl, params);
 }
 
 

 function detect(srcText, callback){ // detect the language of given string(s)
  var callbacksArray = window['jeremy']['language']['callbacks'],
   serviceUrl = BASE_URL + '/detect',
   params = { // process the args
    'q' : typeof(srcText)=='string' || srcText instanceof Array ? srcText : srcText['text'],
    'callback' : 'window.google.language.callbacks['+callbacksArray.length+']',
    'format' : typeof(srcText)=='object' ? srcText['type'] : null,
    'jgAction' : 'detect'
   };
  callbacksArray.push(callback);
  goGetIt(serviceUrl, params);  
 } 

   
   
 function goGetIt(serviceUrl, params, overrideXHR){ // general go-to handler to go and get stuff.
  var args = [];
  params['key'] = window['translateAPIkey'];
  for(var i in params){
   var param = params[i];
   if(param){ // let's weed out the undefined stuff
    if(i == 'q' && param instanceof Array){ // if this is the query string, and we have more than one of them...
     for(var j=0; j<param.length; j++){ // ... we'll loop through them. REMEMBER: The api won't accept more than 128 q values!
      args.push([i+'[]',encodeURIComponent(params[i][j])].join('='));
     }
    }else{
     args.push([i,encodeURIComponent(params[i])].join('='));
    }
   }
  }
  if(!overrideXHR && window['translateProxyUrl']){ // Check if we have a server-side proxy to relay strings for us and use it if possible
   var xhr;
   if(window.XMLHttpRequest){
    xhr = new window.XMLHttpRequest;
   }else{
    try{
     xhr = new ActiveXObject('Msxml2.XMLHTTP');
    }catch(err){
     try{
      xhr = new ActiveXObject('Microsoft.XMLHTTP');
     }catch(err2){
     }
    }
   }
   if(xhr){
    xhr.open('POST',window['translateProxyUrl'],true);
    xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
    xhr.setRequestHeader('Content-length',args.length);
    xhr.onreadystatechange = function(){
     if(xhr.readyState == 4 && xhr.status == 200){
      try{
       eval(xhr.responseText);
      }catch(err){
       throw('Unable to process response: '+xhr.readyState);
      }
     }
    };
    xhr.send(args.join('&'));
    return;
   }
  }
  // If there is no proxy involved, then we fall back to JSONP.
  var script = createEl('script',null,null,{type:'text/javascript'}),
   onload = function(){
    script.onload = null;
    script.parentNode.removeChild(script);
    delete(script)
   },
   onreadystatechange = function(){
    if(script.readystate == 'complete' || script.readystate == 'ready'){
     script.onreadystatechange = null;
     onload();
    }
   };
  script.src = serviceUrl + '?' + args.join('&');
  if(navigator.product=='Gecko'){
   script.onload = onload;
  }else{
   script.onreadystatechange = onreadystatechange;
  }
  document.getElementsByTagName('head')[0].appendChild(script);
 }
 
 function TranslateControl(el, master_el, opt_options){ // BONUS: a translate control that will automatically translate a form input, textarea, or an entire document
  el = this.container = typeof(el)=='string' ? document.getElementById(el) : el;
  master_el = this.master = master_el ? (typeof(master_el)=='string' ? document.getElementById(master_el) : master_el) : document.getElementsByTagName('body')[0];
  master_el.translations = {};
  this.options = opt_options;
  this.control = createEl('form','jTranslateControl',[ // the form for the control
   createEl('div','jTranslateControl',[
    this.select = createEl('select','jTranslateControl jTranslateControl-menu',null,{
     'onchange' : createClosure(this, TranslateControl.prototype['translate'])
    })
   ])
  ],{
   'onsubmit' : function(){return false;} // don't want to go anywhere when the form is submitted
  });
  if(typeof(window['languageEnumCompleteCallback']) == 'function'){ // set up a listener so that we can refresh the language options whenever the Languages enum is refreshed
   var origCallback = window['languageEnumCompleteCallback'];
   window['languageEnumCompleteCallback'] = createClosure(this, function(){
    origCallback();
    this.refreshOptions();
   });
  }else{
   window['languageEnumCompleteCallback'] = createClosure(this, function(){
    this.refreshOptions();
   });
  }
  this.refreshOptions();
  while(el.firstChild){ // remove whatever is in the containing el 
   el.removeChild(el.firstChild);
  }
  el.appendChild(this.control); // append the form to the containing el, and we're done
 }
 TranslateControl.prototype.refreshOptions = function(){
  var langs = window['google']['language']['Languages'],
   sel = this.select;
  if(langs){ // chec if the Languages enum has been populated
   sel.options.length = 0;
   this.addOption_(LABEL_ORIGINAL_LANGUAGE, '');
   for(var i in langs){ // if it's ready, we'll add some options.
    this.addOption_(i, langs[i]);
   }
  }
 };
 TranslateControl.prototype.addOption_ = function(text, val){
  var opt = document.createElement('option');
  opt.text = text;
  opt.value = val;
  this.select.add(opt, null);
 };
 TranslateControl.prototype['translate'] = function(destLang){
  var master = this.master,
   sel = this.select,
   bigString;
  if(destLang){ // if the destLang was passed as an argument, let's change the select accordingly
   for(var i=0; i<sel.options.length; i++){
    if(sel.options[i].value == destLang){
     sel.selectedIndex = i;
     break;
    }
//   sel.selectedIndex = 0; // fallback if the provided destLang isn't available
   }
  }
  destLang = sel.options[sel.selectedIndex].value; // let's make sure we've got the destLang now.
  if(master.translations && master.translations[destLang]){
   master.innerHTML = master.translations[destLang];
  }else if(destLang != VALUE_ORIGINAL_LANGUAGE){ // if we've got a valid destLang, let's run the translation(s)
   if(master.translations[VALUE_ORIGINAL_LANGUAGE]){ // if we've already figured the original value, let's use it.
    bigString = master.translations[VALUE_ORIGINAL_LANGUAGE];
   }else if(master.tagName == 'INPUT' || master.tagName == 'TEXTAREA'){ // if we're dealing with an input or textarea...
    bigString = master.translations[VALUE_ORIGINAL_LANGUAGE] = master.value;
   }else{ // otherwise, we'll just get the innerHTML
    bigString = master.translations[VALUE_ORIGINAL_LANGUAGE] = master.innerHTML;
   }
   var strings = encodeURIComponent(bigString).match(TranslateControl.regex); // uri-encode the big string and break it into smaller strings if necessary
   for(var i=0; i<strings.length; i++){
    strings[i] = decodeURIComponent(strings[i]);
   }
   for(var i=0; i<strings.length; i+=128){
    var endI = strings.length - i;
    if(endI > 128){ // if we've got more than 128 strings, we'll break it into multiple requests. It should be noted, though, that currently, the API won't let you run more than 100K characters per day, or 20 strings of 5K chars each. This is really for future (hopefully) functionality!
     endI = 128;
    }
    translate(strings, '', destLang, createClosure(this, TranslateControl.prototype.callback,[strings, destLang]))
   }
   if(master.translations[VALUE_ORIGINAL_LANGUAGE]){ // restore the original content for the moment
    if(master.tagName == 'INPUT' || master.tagName == 'TEXTAREA'){
     master.value = master.translations[VALUE_ORIGINAL_LANGUAGE];
    }else{
     master.innerHTML = master.translations[VALUE_ORIGINAL_LANGUAGE];
    }
   }
  }
 }
 TranslateControl.prototype.callback = function(origStrings, lang, response){ // let's match up and finish the translations
  var master = this.master
  if(response['data'] && response['data']['translations']){
   var translations = response['data']['translations'];
   for(var i=0; i<translations.length; i++){ // loop through the translations, because we don't really know how far we'll get.
    var origString = decodeURIComponent(origStrings[i]);
     allHtml = master.innerHTML;
    master.innerHTML = allHtml.replace(origString, translations[i]['translatedText']);
   }
   master.translations[lang] = master.innerHTML;
  }else if(response['error']){
   var error = response['error'];
   alert([DEFAULT_ERROR_STRING.replace(/###ERROR_CODE###/g, error['code']), error['message']].join('\n'));
  }
 }
 TranslateControl.regex = window['translateProxyUrl'] ? /(.{0,5000}[\,\.\n])/g : /(.{0,1200}[\,\.\n])/g;
 
 // utility functions
 function createEl(tagName, className, children, attribs){ // create elements
  var el = document.createElement(tagName || 'div');
  if(className){
   el.className = className;
  }
  if(children){
   for(var i=0;i<children.length;i++){
    try{
     el.appendChild(children[i]);
    }catch(err){}
   }
  }
  if(attribs){
   for(var i in attribs){
    if(i.match(/^on/)){
     el[i] = attribs[i];
    }else{
     el.setAttribute(i, attribs[i]);
    }
   }
  }
  return el;
 }
 
 function createClosure(context, method, args){
  if(!args){
   args = [];
  }
  return function(){
   for(var i=0; i<arguments.length; i++){
    args.push(arguments[i]);
   }
   method.apply(context, args);
  }
 }
 
 // export stuff
 if(!window['jeremy']){
  window['jeremy'] = {};
 }
 if(!window['google']){
  window['google'] = {};
 }
 window['google']['language'] = window['jeremy']['language'] = {
  'translate' : translate,
  'detect' : detect,
  'refreshLanguageEnum' : getLanguageEnum,
  'translateControl' : TranslateControl,
  'callbacks' : []
 };


 setTimeout(getLanguageEnum, 0); // Go and get the language enum, but keep working while we do it.

 
})()